package safeCommitter.svnIntegration;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.MessageDialogWithToggle;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.team.core.TeamException;
import org.tigris.subversion.subclipse.core.ISVNLocalResource;
import org.tigris.subversion.subclipse.core.SVNException;
import org.tigris.subversion.subclipse.core.commands.GetStatusCommand;
import org.tigris.subversion.subclipse.core.resources.SVNWorkspaceRoot;
import org.tigris.subversion.subclipse.core.util.File2Resource;
import org.tigris.subversion.subclipse.core.util.Util;
import org.tigris.subversion.subclipse.ui.Policy;
import org.tigris.subversion.subclipse.ui.SVNUIPlugin;
import org.tigris.subversion.subclipse.ui.actions.CommitAction;
import org.tigris.subversion.subclipse.ui.operations.CommitOperation;
import org.tigris.subversion.subclipse.ui.settings.ProjectProperties;
import org.tigris.subversion.subclipse.ui.wizards.dialogs.SvnWizard;
import org.tigris.subversion.subclipse.ui.wizards.dialogs.SvnWizardCommitPage;
import org.tigris.subversion.svnclientadapter.ISVNStatus;
import org.tigris.subversion.svnclientadapter.utils.SVNStatusUtils;

import safeCommitter.core.preferences.SCPreferenceConstants;
import safeCommitter.core.template.Activator;

/**
 * Extends the Subclipse's {@link CommitAction} class in order to:
 * <ul>
 * 
 * <li>Init the SC search phase when the user confirms the commit.
 * <li>Apply the user preferences (Allow blank comments) to the dialog.
 * 
 * </ul>
 * 
 * @see SCSvnWizardDialog
 * 
 * @author Luiz Henrique
 * 
 */
public class SCCommitAction extends CommitAction {

	private CommitDialogReflectionUtils commitDialogReflectionUtils;

	@SuppressWarnings("unchecked")
	private HashMap statusMap;

	public SCCommitAction() {
		try {
			commitDialogReflectionUtils = new CommitDialogReflectionUtils(this);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

	@Override
	public IResource[] getSelectedResources() {
		return super.getSelectedResources();
	}

	@SuppressWarnings("unchecked")
	@Override
	protected IResource[] getModifiedResources(final IResource[] resources,
			final IProgressMonitor iProgressMonitor) throws SVNException {
		List conflictFiles = new ArrayList();
		final List modified = new ArrayList();
		List unversionedFolders = new ArrayList();
		hasUnaddedResources = false;
		for (int i = 0; i < resources.length; i++) {
			IResource resource = resources[i];
			ISVNLocalResource svnResource = SVNWorkspaceRoot
					.getSVNResourceFor(resource);

			// This check is for when the action is called with unmanaged
			// resources
			if (svnResource.getRepository() == null) {
				continue;
			}

			// if only one resource selected, get url. Commit dialog
			// displays this.
			if (resources.length == 1) {
				url = svnResource.getStatus().getUrlString();
				if (url == null || resource.getType() == IResource.FILE)
					url = Util.getParentUrl(svnResource);
			}

			// get adds, deletes, updates and property updates.
			GetStatusCommand command = new GetStatusCommand(svnResource, true,
					false);
			command.run(iProgressMonitor);
			ISVNStatus[] statuses = command.getStatuses();
			for (int j = 0; j < statuses.length; j++) {
				if (SVNStatusUtils.isReadyForCommit(statuses[j])
						|| SVNStatusUtils.isMissing(statuses[j])) {
					IResource currentResource = SVNWorkspaceRoot
							.getResourceFor(resource, statuses[j]);
					if (currentResource != null) {
						ISVNLocalResource localResource = SVNWorkspaceRoot
								.getSVNResourceFor(currentResource);
						if (!localResource.isIgnored()) {
							if (!SVNStatusUtils.isManaged(statuses[j])) {
								hasUnaddedResources = true;
								if (currentResource.getType() != IResource.FILE
										&& !isSymLink(currentResource))
									unversionedFolders.add(currentResource);
								else {
									if (!modified.contains(currentResource)) {
										modified.add(currentResource);
										if (currentResource instanceof IContainer)
											statusMap
													.put(
															currentResource,
															statuses[j]
																	.getPropStatus());
										else
											statusMap
													.put(
															currentResource,
															statuses[j]
																	.getTextStatus());
									}
								}
							} else if (!modified.contains(currentResource)) {
								modified.add(currentResource);
								if (currentResource instanceof IContainer)
									statusMap.put(currentResource, statuses[j]
											.getPropStatus());
								else {
									statusMap.put(currentResource, statuses[j]
											.getTextStatus());
									if (SVNStatusUtils
											.isTextConflicted(statuses[j])) {
										IFile conflictNewFile = (IFile) File2Resource
												.getResource(statuses[j]
														.getConflictNew());
										if (conflictNewFile != null)
											conflictFiles.add(conflictNewFile);
										IFile conflictOldFile = (IFile) File2Resource
												.getResource(statuses[j]
														.getConflictOld());
										if (conflictOldFile != null)
											conflictFiles.add(conflictOldFile);
										IFile conflictWorkingFile = (IFile) File2Resource
												.getResource(statuses[j]
														.getConflictWorking());
										if (conflictWorkingFile != null)
											conflictFiles
													.add(conflictWorkingFile);
									}
								}
							}
						}
					}
				}
			}
		}
		IResource[] unaddedResources = getUnaddedResources(unversionedFolders,
				iProgressMonitor);
		for (int i = 0; i < unaddedResources.length; i++)
			if (!modified.contains(unaddedResources[i]))
				modified.add(unaddedResources[i]);
		Iterator iter = conflictFiles.iterator();
		while (iter.hasNext()) {
			IFile conflictFile = (IFile) iter.next();
			modified.remove(conflictFile);
			statusMap.remove(conflictFile);
		}
		return (IResource[]) modified.toArray(new IResource[modified.size()]);
	}

	@SuppressWarnings("unchecked")
	private IResource[] getUnaddedResources(final List resources,
			final IProgressMonitor iProgressMonitor) throws SVNException {
		final List unadded = new ArrayList();
		final SVNException[] exception = new SVNException[] { null };
		for (Iterator iter = resources.iterator(); iter.hasNext();) {
			IResource resource = (IResource) iter.next();
			if (resource.exists()) {
				// visit each resource deeply
				try {
					resource.accept(new IResourceVisitor() {
						public boolean visit(final IResource aResource) {
							ISVNLocalResource svnResource = SVNWorkspaceRoot
									.getSVNResourceFor(aResource);
							// skip ignored resources and their children
							try {
								if (svnResource.isIgnored())
									return false;
								// visit the children of shared resources
								if (svnResource.isManaged())
									return true;
								if (aResource.getType() == IResource.FOLDER
										&& isSymLink(aResource)) // don't
									// traverse
									// into
									// symlink
									// folders
									return false;
							} catch (SVNException e) {
								exception[0] = e;
							}
							// file/folder is unshared so record it
							unadded.add(aResource);
							return aResource.getType() == IResource.FOLDER;
						}
					}, IResource.DEPTH_INFINITE, false /* include phantoms */);
				} catch (CoreException e) {
					throw SVNException.wrapException(e);
				}
				if (exception[0] != null)
					throw exception[0];
			}
		}
		if (unadded.size() > 0)
			hasUnaddedResources = true;
		return (IResource[]) unadded.toArray(new IResource[unadded.size()]);
	}

	@SuppressWarnings("unchecked")
	public CommitOperation getCommitOperation(final IAction action)
			throws InvocationTargetException, InterruptedException {
		statusMap = new HashMap();
		final IResource resources[] = getSelectedResources();
		final List resourcesToBeAdded = new ArrayList();
		final List resourcesToBeDeleted = new ArrayList();
		if (action != null && !action.isEnabled()) {
			action.setEnabled(true);
		} else {
			run(new IRunnableWithProgress() {

				public void run(final IProgressMonitor monitor)
						throws InvocationTargetException {
					try {
						IResource modified[] = getModifiedResources(resources,
								monitor);
						if (modified.length == 0) {
							MessageDialog.openInformation(getShell(), Policy
									.bind("CommitDialog.title"), Policy
									.bind("CommitDialog.noChanges"));
							commit = false;
						} else {
							ProjectProperties projectProperties = ProjectProperties
									.getProjectProperties(modified[0]);
							commit = scConfirmCommit(modified,
									projectProperties);
						}
						if (commit) {
							for (int i = 0; i < resourcesToCommit.length; i++) {
								IResource resource = resourcesToCommit[i];
								ISVNLocalResource svnResource = SVNWorkspaceRoot
										.getSVNResourceFor(resource);
								if (!svnResource.isManaged())
									resourcesToBeAdded.add(resource);
								if (svnResource.getStatus().isMissing())
									resourcesToBeDeleted.add(resource);
							}

						}
					} catch (TeamException e) {
						throw new InvocationTargetException(e);
					}
				}

			}, true, 2);
			if (!commit)
				return null;
			return new CommitOperation(
					getTargetPart(),
					resources,
					(IResource[]) resourcesToBeAdded
							.toArray(new IResource[resourcesToBeAdded.size()]),
					(IResource[]) resourcesToBeDeleted
							.toArray(new IResource[resourcesToBeDeleted.size()]),
					resourcesToCommit, commitComment, keepLocks);
			// commitOperation.setCanRunAsJob(canRunAsJob);
			// commitOperation.run();
		}
		return null;

	}

	@Override
	public void selectionChanged(final IAction action, final ISelection sel) {
		super.selectionChanged(action, sel);
	}

	protected boolean scConfirmCommit(final IResource[] modifiedResources,
			final ProjectProperties projectProperties) throws SVNException {
		if (onTagPath(modifiedResources)
				&& !MessageDialog.openQuestion(getShell(), Policy
						.bind("CommitDialog.title"), Policy
						.bind("CommitDialog.tag")))
			return false;
		int highestProblemSeverity = getHighestProblemSeverity(modifiedResources);
		IPreferenceStore preferenceStore = SVNUIPlugin.getPlugin()
				.getPreferenceStore();
		switch (highestProblemSeverity) {
		default:
			break;

		case 1: // '\001'
			String allowCommitsWithWarnings = preferenceStore
					.getString("pref_commit_with_warning");
			if ("prompt".equals(allowCommitsWithWarnings)
					|| "never".equals(allowCommitsWithWarnings)) {
				MessageDialogWithToggle warningDialog = MessageDialogWithToggle
						.openYesNoQuestion(shell, Policy
								.bind("CommitWizard.commitResources"), Policy
								.bind("CommitWizard.warningMarkers"), Policy
								.bind("CommitWizard.warningQuestion"), false,
								preferenceStore, "pref_commit_with_warning");
				if (2 != warningDialog.getReturnCode())
					return false;
			}
			break;

		case 2: // '\002'
			String allowCommitsWithErrors = preferenceStore
					.getString("pref_commit_with_errors");
			if (!"prompt".equals(allowCommitsWithErrors)
					&& !"never".equals(allowCommitsWithErrors))
				break;
			MessageDialogWithToggle errorDialog = MessageDialogWithToggle
					.openYesNoQuestion(shell, Policy
							.bind("CommitWizard.commitResources"), Policy
							.bind("CommitWizard.errorMarkers"), Policy
							.bind("CommitWizard.errorQuestion"), false,
							preferenceStore, "pref_commit_with_errors");
			if (2 != errorDialog.getReturnCode())
				return false;
			break;
		}

		SvnWizardCommitPage commitPage = new SvnWizardCommitPage(
				modifiedResources, url, projectProperties, statusMap, null,
				false);
		SvnWizard wizard = new SvnWizard(commitPage);
		SCSvnWizardDialog dialog = new SCSvnWizardDialog(getShell(), wizard);
		String reflectedProposedComment = (String) commitDialogReflectionUtils
				.getField("proposedComment");
		if (reflectedProposedComment == null
				|| reflectedProposedComment.length() == 0)
			commitPage.setComment((String) commitDialogReflectionUtils
					.getProposedMethodValue(modifiedResources));
		else
			commitPage.setComment(reflectedProposedComment);
		wizard.setParentDialog(dialog);
		dialog.create();
		try {
			if (!Activator.getDefault().getPreferenceStore().getBoolean(
					SCPreferenceConstants.P_ALLOW_BLANK_COMMENTS)) {
				dialog.setButtonEnablement(false);
				dialog.addModifyListener();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		boolean commitOK = dialog.open() == 0;
		url = null;
		commitComment = commitPage.getComment();
		resourcesToCommit = commitPage.getSelectedResources();
		keepLocks = commitPage.isKeepLocks();
		return commitOK;
	}

	// private void setDialogProperties(final IResource[] modifiedResources,
	// final SCCommitDialog dialog) {
	// CommitDialogReflectionUtils commitDialogReflectionUtils;
	// try {
	// commitDialogReflectionUtils = new CommitDialogReflectionUtils(this);
	// dialog.setSharing((Boolean)
	// commitDialogReflectionUtils.getField("sharing"));
	// String proposedComment = (String)
	// commitDialogReflectionUtils.getField("proposedComment");
	// if (proposedComment == null || proposedComment.length() == 0) {
	// dialog.setComment((String)
	// commitDialogReflectionUtils.getProposedMethodValue(modifiedResources));
	// } else {
	// dialog.setComment(proposedComment);
	// }
	// } catch (ClassNotFoundException e1) {
	// e1.printStackTrace();
	// }
	// }

	public IResource[] getResourcesToCommit() {
		return resourcesToCommit;
	}

	private boolean onTagPath(final IResource[] modifiedResources)
			throws SVNException {
		// Multiple resources selected.
		if (url == null) {
			final IResource resource = modifiedResources[0];
			final ISVNLocalResource svnResource = SVNWorkspaceRoot
					.getSVNResourceFor(resource);
			String firstUrl = svnResource.getStatus().getUrlString();
			if (firstUrl == null || resource.getType() == IResource.FILE)
				firstUrl = Util.getParentUrl(svnResource);
			if (firstUrl.indexOf("/tags/") != -1)return true; //$NON-NLS-1$
		}
		// One resource selected.
		else if (url.indexOf("/tags/") != -1)return true; //$NON-NLS-1$
		return false;
	}

	/**
	 * Class that perform some reflection operations on the superClass private
	 * fields.
	 * 
	 * TODO: Ask for some integration with subclipse's team.
	 * 
	 * 
	 * @author Luiz Henrique
	 * 
	 */
	private class CommitDialogReflectionUtils {

		Class<?> c;

		CommitAction action;

		public CommitDialogReflectionUtils(final SCCommitAction commitAction)
				throws ClassNotFoundException {
			c = Class
					.forName("org.tigris.subversion.subclipse.ui.actions.CommitAction");
			action = commitAction;
		}

		public Object getField(final String fieldName) {
			try {
				Field declaredField = c.getDeclaredField(fieldName);
				declaredField.setAccessible(true);
				return declaredField.get(action);
			} catch (Exception e) {
				e.printStackTrace();
				return null;
			}
		}

		public Object getProposedMethodValue(final IResource[] modifiedResources) {
			try {
				Method m = c.getDeclaredMethod("getProposedComment",
						IResource[].class);
				m.setAccessible(true);
				return m.invoke(action, (Object) modifiedResources);
			} catch (Exception e) {
				e.printStackTrace();
				return null;
			}

		}

	}
}